using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using FftLib;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
    /// <summary>
    /// [Serializable] : IComparable
    /// </summary>
    [Serializable]
    public class CategoryNode: IComparable,ISerializable,IDeserializationCallback,IXmlSerializable
    {
        [NonSerialized]
        public ObjectParserData Owner;
        /// <summary>
        /// The superordinate cateogory node.  Null if no superordinate exists.
        /// </summary>
        [NonSerialized]
        public CategoryNode Super;
        /// <summary>
        /// The subordinate CategoryNode objects.
        /// </summary>
        public CategoryList Sub;
        /// <summary>
        /// ArrayList of ParsedObject.  Objects associated with this CategoryNode (but not sub-nodes).
        /// When this.IsRootCategory==true, this should be empty (with a Count of zero).
        /// </summary>
        public ParsedObjectList Objects;
        /// <summary>
        /// The index into ParsedObject of the active object.
        /// </summary>
        public int ActiveObjectInd;
        /// <summary>
        /// Gets the PixExcludeNode.  This is the node below the root node.
        /// Returns null if this.IsRootCategory==true.
        /// </summary>
        public CategoryNode PixExcludeNode
        {
            get
            {
                if(this.IsRootCategory) return null;
                CategoryNode cn = this;
                while(true)
                {
                    if(cn.Super.IsRootCategory) return cn;
                    cn = cn.Super;
                }
            }
        }
        /// <summary>
        /// Gets the active object.  Gets null if there are no objects.
        /// </summary>
        public ParsedObject ActiveObject
        {
            get
            {
                if(Objects.Count==0) return null;
                if(ActiveObjectInd < 0)
                    ActiveObjectInd = 0;
                else if(ActiveObjectInd>=Objects.Count)
                    ActiveObjectInd = 0;
                return Objects[ActiveObjectInd];
            }
            set
            {
                ActiveObjectInd = Objects.IndexOf(value);
            }
        }
        /// <summary>
        /// Gets the active polygon.  Returns null if there are no active polygons or active objects.
        /// </summary>
        public PolygonF ActivePoly
        {
            get
            {
                if(ActiveObject==null) return null;
                ParsedObject po = ActiveObject;
                if(po.Polygons==null || po.Polygons.Count==0) return null;
                if(po.ActivePolyInd<0 || po.ActivePolyInd>=po.Polygons.Count)
                    po.ActivePolyInd=0;
                return (PolygonF)po.Polygons[ActiveObject.ActivePolyInd];
            }
        }
        /// <summary>
        /// Gets the index of the active polygon.
        /// </summary>
        public int ActivePolyInd
        {
            get
            {
                if(ActiveObject==null) return -1;
                return ActiveObject.ActivePolyInd;
            }
        }
        private static ParsedObject _poTemp;
        public ContourF ActiveContour
        {
            get
            {
                _poTemp = this.ActiveObject;
                if(_poTemp==null)
                    return null;
                return _poTemp.ActiveContour;
            }
        }
        /// <summary>
        /// The name of this object.
        /// </summary>
        public string Name;
        public CategoryNode() { }
        public CategoryNode(string name,CategoryNode super,ObjectParserData owner)
        {
            Name = name;
            Super = super;
            Sub = new CategoryList();
            Objects = new ParsedObjectList();
            Owner = owner;

            if(Super != null)
                Super.Sub.Insert(0,this);
        }
        public CategoryNode GetSub(string subName)
        {
            string SnCap = subName.ToUpper();
            for(int i=0;i<Sub.Count;i++)
            {
                if(Sub[i].Name.ToUpper() == SnCap)
                {
                    return (CategoryNode)Sub[i];
                }
            }
            return null;
        }
        public CategoryNode GetSubRecursive(string subName)
        {
            CategoryNode output = GetSub(subName);
            if(output != null)
                return output;
            for(int i=0;i<Sub.Count;i++)
            {
                output = Sub[i].GetSubRecursive(subName);
                if(output != null)
                    return output;
            }
            return output;
        }
        public Rectangle GetBoundingRect(bool doRecursion)
        {
            int l, t, r, b, i;
            Rectangle output = Rectangle.Empty;
            Rectangle br;
            
            for (i = 0; i < this.Objects.Count; i++)
            {
                br = this.Objects[i].BoundingRect;
                if (!(br.IsEmpty || br.Width <= 0 || br.Height <= 0))
                    if (output.IsEmpty)
                        output = br;
                    else
                        output = Rectangle.Union(output, br);
            }
            
            if (doRecursion)
                for (i = 0; i < this.Sub.Count; i++)
                    output = Rectangle.Union(output, this.Sub[i].GetBoundingRect(true));
            
            return output;
        }
        #region Serialization
        public CategoryNode(SerializationInfo info,StreamingContext context)
        {	//	info.Get____
            try
            {
                Sub = (CategoryList)info.GetValue("Sub",typeof(CategoryList));
            }
            catch
            {
                OnDeserialization_Sub = (ArrayList)info.GetValue("Sub",typeof(ArrayList));
                Sub = new CategoryList();
            }
            try
            {
                Objects = (ParsedObjectList)info.GetValue("Objects",typeof(ParsedObjectList));
            }
            catch
            {
                OnDeserialization_Objects = (ArrayList)info.GetValue("Objects",typeof(ArrayList));
                Objects = new ParsedObjectList();
            }
            ActiveObjectInd = info.GetInt32("ActiveObjectInd");
            Name = info.GetString("Name");
        }
        public virtual void GetObjectData(SerializationInfo info,StreamingContext context)
        {	//	info.AddValue
            info.AddValue("Sub",Sub);
            info.AddValue("Objects",Objects);
            info.AddValue("ActiveObjectInd",ActiveObjectInd);
            info.AddValue("Name",Name);
        }
        public void OnDeserialization(object sender)
        {
            if(OnDeserialization_Sub != null)
            {
                for(int i=0;i<OnDeserialization_Sub.Count;i++)
                {
                    Sub.Add((CategoryNode)OnDeserialization_Sub[i]);
                    Sub[i].Super = this;
                }
            }
            else
            {
                for(int i=0;i<Sub.Count;i++)
                    Sub[i].Super = this;
            }
            OnDeserialization_Sub = null;

            if(OnDeserialization_Objects != null)
            {
                for(int i=0;i<OnDeserialization_Objects.Count;i++)
                {
                    ParsedObject po = new ParsedObject();
                    po.Name = ((ObjectData)OnDeserialization_Objects[i]).Name;
                    po.ActivePolyInd = ((ObjectData)OnDeserialization_Objects[i]).ActivePolyInd;
                    po.Polygons = new PolygonFarray(((ObjectData)OnDeserialization_Objects[i]).Polygons);
                    for(int j=0;j<po.Polygons.Count;j++)
                        po.Polygons[j].Owner = po;
                    po.SearchText = "";
                    po.Category = this;
                    Objects.Add(po);
                }
            }
            else
                for(int i=0;i<Objects.Count;i++)
                    Objects[i].Category = this;
            OnDeserialization_Objects = null;
        }
        /// <summary>
        /// This command is executed before XML serialization.  It prepares for creation of a new list of VertexF objects, to be executed before 
        /// EnumerateForSerializationVertexF()
        /// </summary>
        /// <param name="doRecursion"></param>
        public void AssignNullSerializationPointers(bool doRecursion)
        {
            int i, j, k, l;
            ParsedObject po;
            PolygonF pf;
            ContourF cf;
            for (i = 0; i < this.Objects.Count; i++)
            {
                po = this.Objects[i];
                for (j = 0; j < po.Polygons.Count; j++)
                {
                    pf = po.Polygons[j];
                    pf.SerializationPointer = -1;
                    for (k = 0; k < pf.Contours.Count; k++)
                    {
                        cf = pf.Contours[k];
                        for (l = 0; l < cf.Count; l++)
                            cf[l].SerializationPointer = -1;
                    }
                    for (k = 0; k < pf.Count; k++)
                        pf[k].SerializationPointer = -1;
                }
            }
            if (doRecursion)
                for (i = 0; i < this.Sub.Count; i++)
                    this.Sub[i].AssignNullSerializationPointers(true);

        }
        /// <summary>
        /// This command is executed before XML serialization.  It creates a list of unique VertexF objects and stores the index
        /// into the 'output' list to all VertexF objects in the 'SerializationPointer' field of each object.
        /// </summary>
        /// <param name="doRecursion"></param>
        /// <param name="output"></param>
        public void EnumerateForSerialization(bool doRecursion, List<VertexF> vertices, List<PolygonF> polygons)
        {
            int i, j, k, l;
            ParsedObject po;
            PolygonF pf;
            ContourF cf;
            for (i = 0; i < this.Objects.Count; i++)
            {
                po = this.Objects[i];
                for (j = 0; j < po.Polygons.Count; j++)
                {
                    pf = po.Polygons[j];
                    if (pf.SerializationPointer == -1)
                    {
                        pf.SerializationPointer = polygons.Count;
                        polygons.Add(pf);
                    }
                    for (k = 0; k < pf.Contours.Count; k++)
                    {
                        cf = pf.Contours[k];
                        for (l = 0; l < cf.Count; l++)
                            if (cf[l].SerializationPointer == -1)
                            {
                                cf[l].SerializationPointer = vertices.Count;
                                vertices.Add(cf[l]);
                            }
                    }
                    for (k = 0; k < pf.Count; k++)
                        if (pf[k].SerializationPointer == -1)
                        {
                            pf[k].SerializationPointer = vertices.Count;
                            vertices.Add(pf[k]);
                        }
                }
            }
            if (doRecursion)
                for (i = 0; i < this.Sub.Count; i++)
                    this.Sub[i].EnumerateForSerialization(true, vertices, polygons);
        }
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            CategoryNode cn;
            ParsedObject po;
            bool reading=false;
            int depth = reader.Depth;
            while((reading || reader.Read()) && reader.Depth>depth)
            {
                reading = false;
                if((reader.NodeType == System.Xml.XmlNodeType.Element || reader.NodeType==System.Xml.XmlNodeType.Text) && reader.Name!="")
                {
                    reading = true;
                    switch(reader.Name)
                    {
                        case "Name":
                            {
                                this.Name = reader.ReadElementContentAsString();
                            } break;
                        case "ActiveObjectInd":
                            {
                                this.ActiveObjectInd = reader.ReadElementContentAsInt();
                            } break;
                        case "Objects":
                            {
                                this.Objects = new ParsedObjectList();
                                //  Step in 1 level and parse each "parsedobject", add it to the list.
                                reading = false;
                            } break;
                        case "ParsedObject":
                            {
                                po = new ParsedObject();
                                this.Objects.Add(po);
                                po.ReadXml(reader); // dummy polygons will be read in, this will be fixed at a higher level.
                                po.Category = this;
                            } break;
                        case "Sub":
                            {
                                this.Sub = new CategoryList();
                                //  Step in 1 level and parse each "category", add it to the list.
                                reading = false;
                            } break;
                        case "Category":
                            {
                                cn = new CategoryNode();
                                cn.Super = this;
                                this.Sub.Add(cn);
                                cn.ReadXml(reader);
                            } break;
                        default: throw new Exception("Unexpected field encounterd while reading XML for a PolygonF object.  Field name = " + reader.Name);
                    }
                }
            }
        }
        /// <summary>
        /// Writes this object as XML, meant to be called from ObjectParserData.WriteXML, which calls two necessary functions prior to serialization:
        /// AssignNullSerializationPointers and then EnumerateForSerialization.
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(System.Xml.XmlWriter writer)
        {
            int i;
            writer.WriteStartElement("Category");

            writer.WriteElementString("Name", this.Name);

            writer.WriteElementString("ActiveObjectInd", this.ActiveObjectInd.ToString());

            writer.WriteStartElement("Objects");
            for (i = 0; i < this.Objects.Count; i++)
                Objects[i].WriteXml(writer);
            writer.WriteEndElement();

            writer.WriteStartElement("Sub");
            for (i = 0; i < this.Sub.Count; i++)
                Sub[i].WriteXml(writer);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
        public void ReplaceDummyVariables(bool doRecursion,List<PolygonF> polygons,List<VertexF> vertices)
        {
            int i,j,k,l;
            ParsedObject po;
            PolygonF poly, poly2;
            ContourF con;
            VertexF vf;
            Edge ed;
            OcclusionBridge br;
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].ReplaceDummyVariables(true,polygons,vertices);
            for(i=0;i<this.Objects.Count;i++)
            {
                po = this.Objects[i];
                for(j=0;j<po.Bridges.Count;j++)
                {
                    br = po.Bridges[j];
                    br.Pf1 = polygons[br.Pf1.SerializationPointer];
                    br.Pf2 = polygons[br.Pf2.SerializationPointer];
                    br.Vf1 = vertices[br.Vf1.SerializationPointer];
                    br.Vf2 = vertices[br.Vf2.SerializationPointer];
                }
                po.Polygons.AutoSort = false;
                for(j=0;j<po.Polygons.Count;j++)
                {
                    poly = po.Polygons[j] = polygons[po.Polygons[j].SerializationPointer];
                    poly.Owner = po;
                    poly.StandardizesAutomatically = false;
                    for(l=0;l<poly.Count;l++)
                    {
                        vf = poly[l] = vertices[poly[l].SerializationPointer];
                        if(vf.Polygons == null)
                            vf.Polygons = new PolyFarray(true);
                        vf.Polygons.Add(poly);
                    }
                    poly.StandardizesAutomatically = true;
                    if(poly.Swallowed == null)
                        poly.Swallowed = new PolygonFarray();
                    for(k=0;k<poly.Swallowed.Count;k++)
                    {
                        poly2 = poly.Swallowed[k] = polygons[poly.Swallowed[k].SerializationPointer];
                        poly2.SwallowedBy = poly;
                    }

                    poly.Contours.AutoSort = false;
                    for(k=0;k<poly.Contours.Count;k++)
                    {
                        con = poly.Contours[k];
                        for(l=0;l<con.Count;l++)
                        {
                            vf = con[l] = vertices[con[l].SerializationPointer];
                            if(vf.Contours == null)
                                vf.Contours = new ContourFarray(true);
                            vf.Contours.Add(con);
                        }
                    }
                    poly.Contours.AutoSort = true;
                    poly.Contours.Sort();

                    for(k=0;k<poly.OwnedEdges.Count;k++)
                    {
                        ed = poly.OwnedEdges[k];
                        if(!ed.IsFullEnclosure && ed.Begin!=null)
                            ed.Begin = vertices[ed.Begin.SerializationPointer];
                        if(!ed.IsFullEnclosure && ed.End!=null)
                            ed.End = vertices[ed.End.SerializationPointer];
                    }
                }
                po.Polygons.AutoSort = true;
                po.Polygons.Sort();
            }
        }
        #endregion
        public void AssignOwnership(ObjectParserData owner)
        {
            this.Owner = owner;
            ParsedObject po;
            PolygonF poly;
            int i,j,k;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    poly = po.Polygons[j];
                    poly.Owner = po;
                    for(k=0;k<poly.Contours.Count;k++)
                    {
                        poly.Contours[k].Owner = po;
                        poly.Contours[k].PolyOwner = poly;
                    }
                }
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].AssignOwnership(owner);
        }
        public void AssignFormParse(IFormParse fp)
        {
            int i,j,k;
            ParsedObject po;
            PolygonF pf;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    pf.Parser = fp;
                    for(k=0;k<pf.Contours.Count;k++)
                        pf.Contours[k].Parser = fp;
                    for(k=0;k<pf.Swallowed.Count;k++)
                        pf.Swallowed[k].Parser = fp;
                }
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].AssignFormParse(fp);
        }
        [NonSerialized]
        public ArrayList OnDeserialization_Sub;
        [NonSerialized]
        public ArrayList OnDeserialization_Objects;
        /// <summary>
        /// The path of this object.  This path is delimited by '/', but does not end with a '/' character.
        /// </summary>
        public string Path
        {
            get
            {
                string output = this.Name;
                CategoryNode cn = this;
                while(cn.Super != null)
                {
                    cn = cn.Super;
                    output = cn.Name + "/" + output;
                }
                return output;
            }
        }
        public bool IsRootCategory
        {
            get
            {
                return Super==null;
            }
        }
        public string PixelExclusiveStr
        {
            get
            {
                if(this.Super==null || this.Super.Super==null)
                    return "[Exclusive]";
                else
                    return "[NotExclusive]";
            }
        }
        public override string ToString()
        {
            return PixelExclusiveStr + Path;
        }
        public int CompareTo(object obj)
        {
            return Name.CompareTo(obj);
        }
        /// <summary>
        /// Inserts and actives an object into the current node.
        /// </summary>
        /// <param name="index">int  The desired index of insertion into the member variable Objects.</param>
        /// <param name="od">ParsedObject  The instance that defines the objects.</param>
        public void InsertAndActivateObject(int index,ParsedObject od)
        {
            Objects.Insert(index,od);
            ActiveObjectInd=index;
        }
        public ParsedObject NextObject
        {
            get
            {
                int i = ActiveObjectInd+1;
                if(i > Objects.Count)
                    i = 0;
                return Objects[i];
            }
        }
        public ParsedObject PreviousObject
        {
            get
            {
                int i = ActiveObjectInd-1;
                if(i < 0)
                    i = Objects.Count-1;
                return Objects[i];
            }
        }
        public PolygonF NextPoly
        {
            get
            {
                if(ActiveObject==null)
                    return null;
                return ActiveObject.NextPoly;
            }
        }
        public PolygonF PreviousPoly
        {
            get
            {
                if(ActiveObject!=null)
                    return ActiveObject.PreviousPoly;
                return null;
            }
        }
        public void RemoveVertexF()
        {
            for(int i=0;i<this.Objects.Count;i++)
                ((ParsedObject)Objects[i]).RemoveVertexF();
            for(int i=0;i<Sub.Count;i++)
                ((CategoryNode)Sub[i]).RemoveVertexF();
        }
        public void ReclaimVertices()
        {
            for(int i=0;i<Objects.Count;i++)
                ((ParsedObject)Objects[i]).ReclaimVertices();
            for(int i=0;i<Sub.Count;i++)
                ((CategoryNode)Sub[i]).ReclaimVertices();
        }
        /// <summary>
        /// Returns the group of polygons in this category containing the point.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public void PolygonsContainingPoint(float x,float y,PolygonFlist output)
        {
            Objects.PolygonsContainingPoint(x,y,output);
            for(int i=0;i<Sub.Count;i++)
                Sub[i].PolygonsContainingPoint(x,y,output);
        }
        public void ReducePix()
        {
            for(int i=0;i<Objects.Count;i++)
                ((ParsedObject)Objects[i]).ReducePix();
            for(int i=0;i<Sub.Count;i++)
                ((CategoryNode)Sub[i]).ReducePix();
        }
        public void PolygonsInRegion(PolygonF region,PolygonFlist output)
        {
            for(int i=0;i<this.Objects.Count;i++)
                Objects[i].PolygonsInRegion(region,output);
            for(int i=0;i<this.Sub.Count;i++)
                Sub[i].PolygonsInRegion(region,output);
        }
        public int NumObjects
        {
            get
            {
                int val = this.Objects.Count;
                for(int i=0;i<Sub.Count;i++)
                    val += ((CategoryNode)Sub[i]).NumObjects;
                return val;
            }
        }
        public int NumObjectsNotResidue
        {
            get
            {
                if(this.Name.ToUpper() == "RESIDUE")
                    return 0;
                int val = this.Objects.Count;
                for(int i=0;i<Sub.Count;i++)
                    val += ((CategoryNode)Sub[i]).NumObjectsNotResidue;
                return val;
            }
        }
        public VertexF ClosestInactiveVertexF(PointF refPt,PathF activePath,bool includeContoursFromInactivePoly)
        {
            VertexF vfMin,vf;
            vfMin = null;
            PolygonF poly;
            ContourF con;
            ParsedObject od;
            int i,j,k,l;
            double dist,minDist=Double.MaxValue;
            bool firstOne = true;
            for(i=0;i<Objects.Count;i++)
            {
                od =(ParsedObject)Objects[i];
                for(j=0;j<od.Polygons.Count;j++)
                {
                    poly = od.Polygons[j];
                    if(poly != activePath)
                    {
                        for(k=0;k<poly.Count;k++)
                        {
                            dist = VertexF.Distance(poly[k],refPt);
                            if(firstOne)
                            {
                                minDist = dist;
                                vfMin = poly[k];
                                firstOne = false;
                            }
                            else if(dist < minDist)
                            {
                                minDist = dist;
                                vfMin = poly[k];
                            }
                        }
                    }
                    if(includeContoursFromInactivePoly || poly!=activePath)
                    {
                        for(l=0;l<poly.Contours.Count;l++)
                        {
                            con = poly.Contours[l];
                            for(k=0;k<con.Count;k++)
                            {
                                //	extra layer of checking to make sure poly is not involved.
                                if(poly!=activePath || !con[k].Polygons.Contains(poly))
                                {
                                    dist = VertexF.Distance(con[k],refPt);
                                    if(firstOne)
                                    {
                                        minDist = dist;
                                        vfMin = con[k];
                                        firstOne = false;
                                    }
                                    else if(dist < minDist)
                                    {
                                        minDist = dist;
                                        vfMin = con[k];
                                    }
                                }
                            }
                        }
                    }
                }
            }

            for(i=0;i<Sub.Count;i++)
            {
                vf = ((CategoryNode)Sub[i]).ClosestInactiveVertexF(refPt,activePath,includeContoursFromInactivePoly);
                if(vf != null)
                {
                    dist = VertexF.Distance(refPt,vf);
                    if(firstOne)
                    {
                        minDist = dist;
                        vfMin = vf;
                        firstOne = false;
                    }
                    else if(dist < minDist)
                    {
                        minDist = dist;
                        vfMin = vf;
                    }
                }
            }

            return vfMin;

        }
        public VertexF ClosestInactiveVertexF(VertexF refPt,PathF activePath,bool includeContoursFromInactivePoly)
        {
            return ClosestInactiveVertexF(new PointF(refPt.X,refPt.Y),activePath,includeContoursFromInactivePoly);

        }
        public void InventoryParsedObjects(ParsedObjectList list)
        {
            for(int i=0;i<this.Sub.Count;i++)
                Sub[i].InventoryParsedObjects(list);
            for(int i=0;i<this.Objects.Count;i++)
                list.Add(Objects[i]);
        }
        public void InventoryContours(ContourFarray list)
        {
            ParsedObjectList pol = new ParsedObjectList();
            InventoryParsedObjects(pol);
            int i,j,k;
            ParsedObject po;
            PolygonF pf;
            ContourF con;
            for(i=0;i<pol.Count;i++)
            {
                po = pol[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    for(k=0;k<pf.Contours.Count;k++)
                    {
                        con = pf.Contours[k];
                        if(con.Count>0)
                            list.Add(con);
                    }
                }
            }
        }
        public void ResetPixelTagger()
        {
            for(int i=0;i<this.Sub.Count;i++)
                Sub[i].ResetPixelTagger();
            for(int i=0;i<this.Objects.Count;i++)
                Objects[i].ResetPixelTagger();
        }
        public void ObjectsCategorizedAs(ParsedObjectList output,FieldInfo catField,object catFieldVal)
        {
            int i;
            for(i=0;i<Objects.Count;i++)
                if(catField.GetValue(Objects[i].CatData).GetHashCode() == catFieldVal.GetHashCode())
                    output.Add(Objects[i]);
            for(i=0;i<Sub.Count;i++)
                Sub[i].ObjectsCategorizedAs(output,catField,catFieldVal);
        }
        public void ContoursCategorizedAs(ContourFarray output,FieldInfo catField,object catFieldVal)
        {
            int i,ctStart=output.Count;
            InventoryContours(output);
            for(i=ctStart;i<output.Count;i++)
                if(catField.GetValue(((ContourF)output[i]).CatData).GetHashCode() != catFieldVal.GetHashCode())
                    output.RemoveAt(i--);
        }
        public void AssignUniqueIds(ref uint nextObjectId,ref uint nextPolyId)
        {
            int i;
            for(i=0;i<Objects.Count;i++)
            {
                Objects[i].DbCache_UniqueId = nextObjectId++;
                Objects[i].DbCache_MaskFileName = "po" + Util.PaddedIntegerString((uint)Objects[i].DbCache_UniqueId,10u) +".png";
                Objects[i].AssignUniqueIdPoly(ref nextPolyId);
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].AssignUniqueIds(ref nextObjectId,ref nextPolyId);
        }
        /// <summary>
        /// This assumes that AssignUniqueIds has already been called.
        /// </summary>
        /// <param name="pathname">The path name of the masks.  This algorithm creates a subdirectory for ParsedObjects.</param>
        /// <param name="infoOutput">An ArrayList of strings, each string contains information about a polygon.</param>
        public void SaveMasks(string pathname,ArrayList infoOutput)
        {
            int i;
            string objDir = pathname + "\\ParsedObjects";
            if(!Directory.Exists(objDir))
                Directory.CreateDirectory(objDir);
            string polyDir = pathname + "\\Polygons";
            if(!Directory.Exists(polyDir))
                Directory.CreateDirectory(polyDir);
            for(i=0;i<Objects.Count;i++)
            {
                Objects[i].SavePngMask(objDir + "\\" + Objects[i].DbCache_MaskFileName);
                if(infoOutput!=null)
                    infoOutput.Add(Objects[i].OutputTextShort);
                Objects[i].SaveMasksPoly(polyDir);
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].SaveMasks(pathname,infoOutput);
        }
        public void AddData(ParsedObjectDbTable po,PolygonDbTable poly,uint imageId)
        {
            int i,j;
            for(i=0;i<this.Objects.Count;i++)
            {
                po.AddDataRow(this.Objects[i],imageId);
                for(j=0;j<this.Objects[i].Polygons.Count;j++)
                {
                    poly.AddDataRow(this.Objects[i].Polygons[j]);
                }
            }
            for(i=0;i<this.Sub.Count;i++)
                Sub[i].AddData(po,poly,imageId);
        }
        public void FindUnstandardizedPolygons(PolygonFarray output)
        {
            for(int i=0;i<this.Objects.Count;i++)
                Objects[i].FindUnstandardizedPolygons(output);
            for(int i=0;i<this.Sub.Count;i++)
                Sub[i].FindUnstandardizedPolygons(output);
        }
        /// <summary>
        /// After a CategoryNode is deleted or undeleted, this is useful in finding all of the
        /// EdgeGroup objects that have been invalidated.
        /// </summary>
        /// <returns></returns>
        public EdgeGroupArray AfterChange_ValidateEdges()
        {
            int i,j,k;
            EdgeGroupArray output = new EdgeGroupArray();
            EdgeGroupArray thisOut;
            ParsedObject po;
            PolygonF pf;
            int horse=0;
            for(i=0;i<Objects.Count;i++)
            {
                po= Objects[i];
                if(po.Name=="obj_18")
                    horse = 1;
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    thisOut = pf.ValidateEdges();
                    for(k=0;k<thisOut.Count;k++)
                        output.Add(thisOut[k]);
                }
            }
            for(i=0;i<this.Sub.Count;i++)
                output.AddRange(Sub[i].AfterChange_ValidateEdges());

            //	Get only uniques
            output.Sort();
            for(i=1;i<output.Count;i++)
                if(output[i]==output[i-1])
                    output.RemoveAt(i--);

            return output;
        }
        /// <summary>
        /// This checks to make sure each polygon's OwnedEdges list doesn't have duplicate entries.
        /// This was written with the intention that it would be called before file saving.  However, the new ObjectParser.Command
        /// layer seems to insure against such duplicate entries.  Nevertheless, calling this function before saving is a wise precaution.
        /// </summary>
        /// <returns></returns>
        public void EliminateRedundantOwnedEdges()
        {
            int i,j,k,l;
            ParsedObject po;
            PolygonF pf;
            for(i=0; i<this.Objects.Count; i++)
            {
                po=this.Objects[i];
                for(j=0; j<po.Polygons.Count; j++)
                {
                    pf = po.Polygons[j];
                    for(k=0; k<pf.OwnedEdges.Count-1; k++)
                        for(l=k+1; l<pf.OwnedEdges.Count; l++)
                            if( pf.OwnedEdges[l]==pf.OwnedEdges[k] )
                                pf.OwnedEdges.RemoveAt(l--);
                }
            }
            for(i=0; i<this.Sub.Count; i++)
                Sub[i].EliminateRedundantOwnedEdges();
        }
        /// <summary>
        /// Eliminates redundant vertices.
        /// </summary>
        /// <returns>int  The number of vertices eliminated.</returns>
        public int EliminateRedundantVertices()
        {
            int output = 0;
            float dx,dy;
            int i,j,k,l,ind,backInd,remInd;
            Edge fe;
            ParsedObject po;
            PolygonF poly;
            ContourF con;
            VertexF vf,vfRed;
            for(i=0;i<this.Objects.Count;i++)
            {
                po = this.Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    poly = po.Polygons[j];
                    for(k=0;k<poly.Count;k++)
                    {
                        if(k==0)
                            backInd = poly.Count-1;
                        else
                            backInd = k-1;
                        dx = poly[k].X-poly[backInd].X;
                        dy = poly[k].Y-poly[backInd].Y;
                        dx = dx*dx+dy*dy;
                        if(dx<0.001f)	//	redundant
                        {
                            //	The redundant vertex poly[k].
                            vfRed = poly[k];
                            //	The good vertex
                            vf = poly[backInd];
                            //	Count the vertices removed
                            output++;
                            //	If vf and vfRed are the same vertex, just alter the owners
                            //	and make sure the list doesn't contain double entries.
                            if(vf==vfRed)
                            {
                                //	Remove
                                poly.RemoveAt(k--);
                                remInd=-1;
                                //	Remove redundant Owners
                                for(l=0;l<vfRed.Polygons.Count;l++)
                                {
                                    if(vfRed.Polygons[l]==poly)
                                    {
                                        if(remInd>=0)
                                            vfRed.Polygons.RemoveAt(l--);
                                        else
                                            remInd=l;
                                    }
                                }
                            }
                            else
                            {
                                //	Consolidate the list of polygons
                                for(l=0;l<vfRed.Polygons.Count;l++)
                                {
                                    //	Get the index of vfRed
                                    ind = vfRed.Polygons[l].IndexOf(vfRed);
                                    //	If vf is NOT already a part of the polygon, use it to replace vfRed.
                                    if(!vfRed.Polygons[l].Contains(vf))
                                    {
                                        vfRed.Polygons[l][ind]=vf;
                                        vf.Polygons.Add(vfRed.Polygons[l]);
                                    }
                                    //	Otherwise, just remove vfRed.
                                    else
                                        vfRed.Polygons[l].Remove(vfRed);
                                }
                                //	Consolidate the list of contours
                                for(l=0;l<vfRed.Contours.Count;l++)
                                {
                                    //	Get the index of vfRed
                                    ind = vfRed.Contours[l].IndexOf(vfRed);
                                    //	If vf is NOT already a part of the polygon, use it to replace vfRed.
                                    if(!vfRed.Contours[l].Contains(vf))
                                    {
                                        vfRed.Contours[l][ind]=vf;
                                        vf.Contours.Add(vfRed.Contours[l]);
                                    }
                                    //	Otherwise, just remove vfRed.
                                    else
                                        vfRed.Contours[l].Remove(vfRed);
                                }
                                //	Change any edge elements that referred to vfRed to refer now to vf.
                                for(l=0;l<poly.OwnedEdges.Count;l++)
                                {
                                    fe = poly.OwnedEdges[l];
                                    if(fe.Begin==vfRed)
                                    {
                                        //	Remove the OwnedEdge if vf and vfRed were both demarcating the begin and end.
                                        if(fe.End==vf)
                                            poly.OwnedEdges.RemoveAt(l--);
                                        else
                                            fe.Begin=vf;
                                    }
                                    if(fe.End==vfRed)
                                    {
                                        //	Remove the OwnedEdge if vf and vfRed were both demarcating the begin and end.
                                        if(fe.Begin==vf)
                                            poly.OwnedEdges.RemoveAt(l--);
                                        else
                                            fe.End=vf;
                                    }
                                }
                            }
                        }
                    }
                    for(int jj=0;jj<poly.Contours.Count;jj++)
                    {
                        con = poly.Contours[jj];
                        for(k=0;k<con.Count;k++)
                        {
                            if(k==0)
                                backInd = con.Count-1;
                            else
                                backInd = k-1;
							//	If this is not a closed contour and we are not on the first vertex
							if(k!=0 || con[k]!=con[backInd])
							{
								dx = con[k].X-con[backInd].X;
								dy = con[k].Y-con[backInd].Y;
								dx = dx*dx+dy*dy;
								if(dx<0.001f)	//	redundant
								{
									//	The redundant vertex con[k].
									vfRed = con[k];
									//	The good vertex
									vf = con[backInd];
									//	Count the vertices removed
									output++;
									//	If vf and vfRed are the same vertex, just alter the owners
									//	and make sure the list doesn't contain double entries.
									if(vf==vfRed)
									{
										//	Remove
										con.RemoveAt(k--);
										remInd=-1;
										//	Remove redundant Owners
										for(l=0;l<vfRed.Contours.Count;l++)
										{
											if(vfRed.Contours[l]==con)
											{
												if(remInd>=0)
													vfRed.Contours.RemoveAt(l--);
												else
													remInd=l;
											}
										}
									}
									else
									{
										//	Consolidate the list of polygons
										for(l=0;l<vfRed.Polygons.Count;l++)
										{
											//	Get the index of vfRed
											ind = vfRed.Polygons[l].IndexOf(vfRed);
											//	If vf is NOT already a part of the polygon, use it to replace vfRed.
											if(!vfRed.Polygons[l].Contains(vf))
											{
												vfRed.Polygons[l][ind]=vf;
												vf.Polygons.Add(vfRed.Polygons[l]);
											}
											//	Otherwise, just remove vfRed.
											else
												vfRed.Polygons[l].Remove(vfRed);
										}
										//	Consolidate the list of contours
										for(l=0;l<vfRed.Contours.Count;l++)
										{
											//	Get the index of vfRed
											ind = vfRed.Contours[l].IndexOf(vfRed);
											//	If vf is NOT already a part of the polygon, use it to replace vfRed.
											if(!vfRed.Contours[l].Contains(vf))
											{
												vfRed.Contours[l][ind]=vf;
												vf.Contours.Add(vfRed.Contours[l]);
											}
											//	Otherwise, just remove vfRed.
											else
												vfRed.Contours[l].Remove(vfRed);
										}
									}
								}
							}
                        }
                    }
                }
            }
            for(i=0;i<this.Sub.Count;i++)
                output += this.Sub[i].EliminateRedundantVertices();
            return output;
        }
        public void ValidateBridges(bool doRecursion, OcclusionBridgeList output)
        {
            int i,j;
            OcclusionBridgeList obl;
            for(i=0;i<this.Objects.Count;i++)
            {
                obl = this.Objects[i].ValidateBridges();
                if(obl!=null)
                    for(j=0;j<obl.Count;j++)
                        output.Add(obl[j]);
            }
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].ValidateBridges(true,output);
        }
        public PolygonF FirstPoly
        {
            get
            {
                int i;
                for(i=0;i<Objects.Count;i++)
                {
                    if(Objects[i].Polygons.Count>0)
                        return Objects[i].Polygons[0];
                }
                PolygonF pf;
                for(i=0;i<Sub.Count;i++)
                {
                    pf = Sub[i].FirstPoly;
                    if(pf != null)
                        return pf;
                }
                return null;
            }
        }
        public PolygonF LastPoly
        {
            get
            {
                int i;
                PolygonF pf;
                for(i=Sub.Count-1;i>=0;i--)
                {
                    pf = Sub[i].LastPoly;
                    if(pf != null)
                        return pf;
                }
                for(i=Objects.Count-1;i>=0;i--)
                {
                    if(Objects[i].Polygons.Count>0)
                        return Objects[i].Polygons[Objects[i].Polygons.Count-1];
                }
                return null;
            }
        }
        public void RemoveEmptyPolygonsAndObjects()
        {
            int i,j;
            ParsedObject po;
            PolygonF pf;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    //	Remove empty polygons
                    if(pf.Empty)
                        po.Polygons.RemoveAt(j--);
                }
                //	Remove empty objects.
                if(po.Polygons.Count==0)
                    Objects.RemoveAt(i--);
            }
            //	Recursion
            for(i=0;i<Sub.Count;i++)
                Sub[i].RemoveEmptyPolygonsAndObjects();
        }
        /// <summary>
        /// Finds the owned edges in this category.
        /// </summary>
        /// <param name="output"></param>
        public void FindOwnedEdges(EdgeArray output)
        {
            int i,j,k;
            ParsedObject po;
            PolygonF pf;
            for(i=0;i<this.Objects.Count;i++)
            {
                po= this.Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    for(k=0;k<pf.OwnedEdges.Count;k++)
                    {
                        output.Add(pf.OwnedEdges[k]);
                    }
                }
            }
            for(i=0;i<this.Sub.Count;i++)
                Sub[i].FindOwnedEdges(output);
        }
        /// <summary>
        /// Caches filters to edge elements.
        /// </summary>
        /// <param name="filters">The set of filters to pick from.</param>
        /// <param name="threshold">The threshold distance for grouping, measured in factors of the pixel period
        /// for each edge filter.</param>
        /// <param name="pathPrecision">The effective resolution of hand-segmented edges.  Set to 1.0f
        /// if sampling the hand-segmented edges at a rate of every 1.0f pixels is okay.  Values
        /// closer to zero imply higher precision, further from zero are lower precision.  Value
        /// must be positive.  Reccommend 0.5f.</param>
        public void CacheFiltersOntoEdges(
            FilterResponseList filters,float threshold,float pathPrecision,
            FilterResponseList claimed,FilterResponseList unclaimed,Util.ProgressUpdater updater)
        {
            float left,right,top,bottom,distMin,dist,dx,dy,
                cos,sin,bk,bl,bf,pathDist,distl,distk,wk;
            PointF pterp=PointF.Empty;
            int i,j,jMin,k,kMin,l;
            Edge ed;
            RectangleF rectf;
            FilterResponse fr;
            //	Get all the owned edges.
            EdgeArray edges = new EdgeArray();
            this.FindOwnedEdges(edges);
            #region Compute the maximum number of pixels per cycle
            float maxPeriod= ((FilterResponse)filters[0]).PixelsPerCycle;
            for(i=1;i<filters.Count;i++)
            {
                fr = (FilterResponse)filters[i];
                if(fr.PixelsPerCycle>maxPeriod)
                    maxPeriod = fr.PixelsPerCycle;
            }
            #endregion
            #region Cache the filter distances
            float dPath = pathPrecision;
            float carryOver = dPath;
            for(i=0;i<edges.Count;i++)
            {
                updater((float)i/(float)edges.Count*0.8f);
                ed = edges[i];
                ed.CachedEdgePoints = ed.EdgePoints(dPath,ref carryOver);
                ed.CachedFilterDistances = new float[filters.Count];
                ed.CachedFilterPathDistances = new float[filters.Count];
                ed.CachedFilters = new FilterResponseList();
                //	Bounding rectangle of an edge.  Rules out many cases without iterating too much.
                rectf = ed.BoundingRect(threshold*maxPeriod);
                left = rectf.Left;
                right = rectf.Right;
                top = rectf.Top;
                bottom = rectf.Bottom;

                //	For each filter
                for(j=0;j<filters.Count;j++)
                {
                    //	Only if the spine has points.
                    if(ed.CachedEdgePoints.Length>0)
                    {
                        fr = filters[j];
                        //	See if the filter is inside of the bounding rectangle.
                        if(fr.Location.X>=left && fr.Location.X<=right
							    && fr.Location.Y>=top && fr.Location.Y<=bottom)
                        {
                            k=kMin=0;
                            distMin=Util.DistanceF.Distance(ed.CachedEdgePoints[k],fr.Location);
                            //	Go through the edge points and find the minimum distance.
                            for(k=1;k<ed.CachedEdgePoints.Length;k++)
                            {
                                dist = Util.DistanceF.Distance(ed.CachedEdgePoints[k],fr.Location);
                                if(dist<distMin)
                                {
                                    distMin=dist;
                                    kMin=k;
                                }
                            }
                            //	First guess at the path distance.
                            pathDist=(float)kMin/dPath;
                            //	Interpolate the real minimum distance.
                            if(kMin!=0)
                            {
                                k=kMin-1;
                                l=kMin;
                                dx = (ed.CachedEdgePoints[l].X-ed.CachedEdgePoints[k].X)/dPath;
                                dy = (ed.CachedEdgePoints[l].Y-ed.CachedEdgePoints[k].Y)/dPath;
                                //	compute the perpendiculars
                                cos=dy/dPath;
                                sin=-dx/dPath;
                                bk = sin*ed.CachedEdgePoints[k].X-cos*ed.CachedEdgePoints[k].Y;
                                bl = sin*ed.CachedEdgePoints[l].X-cos*ed.CachedEdgePoints[l].Y;
                                //	is it between the perpendiculars?
                                if(bk<0.0f != bl<0.0f)
                                {
                                    //	compute the constant for a point on the line h(x)=0.
                                    bf = (dy*ed.CachedEdgePoints[k].X-dx*ed.CachedEdgePoints[k].Y);
                                    //	compute the interpolated distance h(x).
                                    dist = Math.Abs((dy*fr.Location.X-dx*fr.Location.Y)-bf);
                                    //	check for programming error with roundoff error
                                    if(dist>distMin+0.05f)
                                        throw new ApplicationException("Problem caching filters to edges.");
                                    distMin=dist;
                                    //	perpendicular slope to sit on the line.
                                    pterp.X = fr.Location.X+dist*sin;
                                    pterp.Y = fr.Location.Y-dist*cos;
                                    //	compute distance to either vertex
                                    distk=Util.DistanceF.Distance(pterp,ed.CachedEdgePoints[k]);
                                    distl=Util.DistanceF.Distance(pterp,ed.CachedEdgePoints[l]);
                                    //	Weight for respective path distances.
                                    wk=distl/(distl+distk);
                                    //	Interpolate
                                    pathDist = (wk*(float)k+(1.0f-wk)*(float)l)/dPath;
                                }
                            }
                            if(kMin!=ed.CachedEdgePoints.Length-1)
                            {
                                //	This is the same code as above except the following two lines are different.
                                k=kMin;
                                l=kMin+1;
                                //	THIS IS EXACTLY THE SAME CODE AS THE PREVIOUS "IF" STATEMENT.
                                dx = ed.CachedEdgePoints[l].X-ed.CachedEdgePoints[k].X;
                                dy = ed.CachedEdgePoints[l].Y-ed.CachedEdgePoints[k].Y;
                                //	compute the perpendiculars, rotate rightwards 90 degrees
                                cos=dy;
                                sin=-dx;
                                bk = sin*ed.CachedEdgePoints[k].X-cos*ed.CachedEdgePoints[k].Y;
                                bl = sin*ed.CachedEdgePoints[l].X-cos*ed.CachedEdgePoints[l].Y;
                                //	is it between the perpendiculars?
                                if(bk<0.0f!=bl<0.0f)
                                {
                                    //	compute the constant for a point on the line h(x)=0.
                                    bf = (dy*ed.CachedEdgePoints[k].X-dx*ed.CachedEdgePoints[k].Y);
                                    //	compute the interpolated distance h(x).
                                    dist = Math.Abs((dy*fr.Location.X-dx*fr.Location.Y)-bf);
                                    //	check for programming error with roundoff error
                                    if(dist>distMin+0.0001f)
                                        throw new ApplicationException("Problem caching filters to edges.");
                                    distMin=dist;
                                    //	perpendicular slope to sit on the line.
                                    pterp.X = fr.Location.X+dist*sin;
                                    pterp.Y = fr.Location.Y-dist*cos;
                                    //	compute distance to either vertex
                                    distk=Util.DistanceF.Distance(pterp,ed.CachedEdgePoints[k]);
                                    distl=Util.DistanceF.Distance(pterp,ed.CachedEdgePoints[l]);
                                    //	Weight for respective path distances.
                                    wk=distl/(distl+distk);
                                    //	Interpolate
                                    pathDist = (wk*(float)k+(1.0f-wk)*(float)l)/dPath;
                                }
                            }
                            //	Store the results.
                            ed.CachedFilterDistances[j]=distMin;
                            ed.CachedFilterPathDistances[j]=pathDist;
                        }
                        else	//	Edge's bounding rect comes nowhere near the filter, store dummy.
                        {
                            //	indicate that the distance is greater than threshold.
                            ed.CachedFilterDistances[j]=Single.PositiveInfinity;
                            ed.CachedFilterPathDistances[j]=-1.0f;
                        }
                    }
                    else	//	Edge spine is retarded, store dummy
                    {
                        //	indicate that the distance is greater than threshold.
                        ed.CachedFilterDistances[j]=Single.PositiveInfinity;
                        ed.CachedFilterPathDistances[j]=-1.0f;
                    }
                }
            }
            #endregion
            #region Select the filter responses, in order.
            float distThresh;
            if(edges.Count>0)
            {
                for(i=0;i<filters.Count;i++)
                {
                    updater((float)i/(float)filters.Count*0.2f + 0.8f);
                    fr = filters[i];
                    distThresh = threshold * fr.PixelsPerCycle;
                    j=0;
                    distMin = edges[j].CachedFilterDistances[i];
                    jMin=j;
                    for(j=1;j<edges.Count;j++)
                    {
                        dist = edges[j].CachedFilterDistances[i];
                        if(dist<distMin)
                        {
                            distMin=dist;
                            jMin=j;
                        }
                    }
                    if(distMin<=distThresh)
                    {
                        claimed.Add(fr);
                        ed = edges[jMin];
                        fr.PathDistanceAlongSegmentedEdge=ed.CachedFilterPathDistances[i];
                        fr.DistanceToSegmentedEdge=ed.CachedFilterDistances[i];
                        ed.CachedFilters.Add(fr);
                    }
                    else
                    {
                        unclaimed.Add(fr);
                        fr.PathDistanceAlongSegmentedEdge=-1.0f;
                        fr.DistanceToSegmentedEdge=Single.PositiveInfinity;
                    }
                }
            }
            #endregion
            #region Release unnecessary caches from service
            for(i=0;i<edges.Count;i++)
            {
                ed = edges[i];
                ed.CachedEdgePoints=null;
                ed.CachedFilterDistances=null;
                ed.CachedFilterPathDistances=null;
            }
            #endregion
        }
        /// <summary>
        /// Called before saving because some of the OwnedEdges may not be valid anymore.
        /// It is CRITICAL that the caller function call RootCategory.CalcEdgeGroups() first.
        /// </summary>
        public void TidyOwnedEdges()
        {
            int i,j;
            ParsedObject po;
            PolygonF pf;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    pf.TidyOwnedEdges();
                }
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].TidyOwnedEdges();
        }
        /// <summary>
        /// Calculates all the edge pairs
        /// </summary>
        public void CalcEdgeGroups()
        {
            int i,j;
            ParsedObject po;
            PolygonF pf;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf = po.Polygons[j];
                    pf.CalcEdges();
                }
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].CalcEdgeGroups();
        }
        /// <summary>
        /// Fills the ArrayList with PointF[] arrays, each array corresponds to a PolygonF.
        /// </summary>
        /// <param name="dpix"></param>
        /// <param name="output"></param>
        public void EdgePoints(float dpix,ArrayList output)
        {
            int i,j,k;
            ParsedObject po;
            PolygonF pf;
            Edge ed;
            float carryOver;
            for(i=0;i<Objects.Count;i++)
            {
                po = Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    pf=po.Polygons[j];
                    for(k=0;k<pf.OwnedEdges.Count;k++)
                    {
                        ed = pf.OwnedEdges[k];
                        if(ed.ClaimedByOwner)
                        {
                            carryOver = dpix;
                            output.Add(ed.EdgePoints(dpix,ref carryOver));
                        }
                    }
                }
            }
            for(i=0;i<Sub.Count;i++)
                Sub[i].EdgePoints(dpix,output);
        }
        public CategoryNode GetSubFromFullPath(string[] names)
        {
            int ct=0;
            CategoryNode cn = this;
            if(names[ct]!=this.Name)
                cn = GetSub(names[ct]);
            while(++ct < names.Length && cn!=null)
                cn = cn.GetSub(names[ct]);
            return cn;
        }
        /// <summary>
        /// For each ParsedObject in this.Objects, its SurfaceBoundList is evaluated, and the best
        /// ribbon section is returned (if it meets the minimum length requirement).  The section
        /// is centered.
        /// </summary>
        /// <param name="doRecursion">If true, the sub-categories are evaluated also.</param>
        /// <param name="ribbons"></param>
        /// <param name="sections"></param>
        /// <param name="minimumLength"></param>
        /// <param name="dPix"></param>
        /// <param name="corePixels"></param>
        /// <param name="directionalDistances"></param>
        /// <param name="sdCurvatureKernel"></param>
        /// <param name="ind1"></param>
        /// <param name="ind2"></param>
        public void GetBestRibbonSections(bool doRecursion,
            ArrayList ribbons,ArrayList sections,int minimumLength,float dPix,
            Ushort3 corePix,float rgbFac,float[] directionalDistances,float sdCurvatureKernel,
            int[] ind1,int[] ind2,ColorSpace[] spaces,ComplexD[][,] filters,int filterLength,PointF[] filterOffsets)
        {
            int i;
            Ribbon rib;
            int[] pos;
            for(i=0;i<Objects.Count;i++)
            {
                SurfaceBoundList sbl = new SurfaceBoundList(Objects[i],dPix);
                sbl.CalcRibbons(corePix,rgbFac,directionalDistances,sdCurvatureKernel,spaces,filters,filterLength,filterOffsets);
                if(sbl.IsValid && sbl.BestSection(ind1,ind2,out rib,out pos))
                {
                    if(pos[1]-pos[0]+1>=minimumLength)
                    {
                        ribbons.Add(rib);
                        sections.Add(pos);
                    }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetBestRibbonSections(true,
                        ribbons,sections,minimumLength,dPix,
                        corePix,rgbFac,directionalDistances,sdCurvatureKernel,
                        ind1,ind2,spaces,filters,filterLength,filterOffsets);
                }
            }
        }
        /// <summary>
        /// For each ParsedObject in this.Objects, its list of contours is evaluated.  Each
        /// contour returns a full ribbon of length determined by that contour.
        /// </summary>
        /// <param name="doRecursion">If true, the sub-categories are evaluated also.</param>
        /// <param name="ribbons"></param>
        /// <param name="minimumLength"></param>
        /// <param name="dPix"></param>
        /// <param name="heightProportion"></param>
        /// <param name="corePixels"></param>
        /// <param name="directionalDistances"></param>
        /// <param name="sdCurvatureKernel"></param>
        /// <param name="spaces">The color spaces for which the ribbons must be computed.</param>
        public void GetFullContourRibbons(bool doRecursion,
            ArrayList ribbons,int minimumLength, float dPix, double heightProportion,
            Ushort3 corePix,float rgbFac, float sdOrientationKernel, ColorSpace[] spaces)
        {
            int i,j,k,l;
            Ribbon rib;
            ParsedObject po;
            PolygonF poly;
            ContourF con;
            PointF[] spine;
            int sideLen, ind;
            float[] directionalDistances;
            for(i=0;i<Objects.Count;i++)
            {
                po= Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    poly = po.Polygons[j];
                    for(k=0;k<poly.Contours.Count;k++)
                    {
                        con = poly.Contours[k];
                        if((int)con.Circumfrence >= minimumLength)
                        {
                            spine = con.EdgePoints(dPix);
                            sideLen = (int)(heightProportion*spine.Length);
                            directionalDistances = new float[2*sideLen+1];
                            directionalDistances[sideLen] = 0.0f;
                            for(ind=0; ind<sideLen; ind++)
                            {
                                directionalDistances[ind] = ind-sideLen;
                                directionalDistances[2*sideLen-ind] = sideLen-ind;
                            }
                            if( sideLen > 0 )
                            {
                                rib = new Ribbon(po,con,corePix,rgbFac,(double)dPix,spine,
                                    directionalDistances,sdOrientationKernel,null,0,null);
                                for(l=0;l<spaces.Length;l++)
                                    rib.CalcColorRibbons(spaces[l]);
                                ribbons.Add(rib);
                            }
                        }
                    }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetFullContourRibbons(true,ribbons,minimumLength,dPix,heightProportion,
                        corePix,rgbFac,sdOrientationKernel,spaces);
                }
            }
        }
        /// <summary>
        /// For each ParsedObject in this.Objects, its list of contours is evaluated.  Each
        /// contour returns a full ribbon of length determined by that contour.
        /// </summary>
        /// <param name="doRecursion">If true, the sub-categories are evaluated also.</param>
        /// <param name="ribbons"></param>
        /// <param name="minimumLength"></param>
        /// <param name="dPix"></param>
        /// <param name="corePixels"></param>
        /// <param name="directionalDistances"></param>
        /// <param name="sdCurvatureKernel"></param>
        /// <param name="spaces">The color spaces for which the ribbons must be computed.</param>
        public void GetFullContourRibbons(bool doRecursion,
            ArrayList ribbons,int minimumLength,float dPix,
            Ushort3 corePix,float rgbFac,float[] directionalDistances,float sdOrientationKernel,
            ColorSpace[] spaces,ComplexD[][,] filters,int filterLength,PointF[] filterOffsets)
        {
            int i,j,k,l;
            Ribbon rib;
            ParsedObject po;
            PolygonF poly;
            ContourF con;
            for(i=0;i<Objects.Count;i++)
            {
                po= Objects[i];
                for(j=0;j<po.Polygons.Count;j++)
                {
                    poly = po.Polygons[j];
                    for(k=0;k<poly.Contours.Count;k++)
                    {
                        con = poly.Contours[k];
                        if((int)con.Circumfrence >= minimumLength)
                        {
                            rib = new Ribbon(po,con,corePix,rgbFac,(double)dPix,con.EdgePoints(dPix),
                                directionalDistances,sdOrientationKernel,filters,filterLength,filterOffsets);
                            for(l=0;l<spaces.Length;l++)
                                rib.CalcColorRibbons(spaces[l]);
                            ribbons.Add(rib);
                        }
                    }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetFullContourRibbons(true,ribbons,minimumLength,dPix,
                        corePix,rgbFac,directionalDistances,sdOrientationKernel,spaces,filters,filterLength,filterOffsets);
                }
            }
        }
        public void GetFullSurfaceRibbons(bool doRecursion,
            ArrayList ribbons,int minimumLength,float dPix,double heightRatio,
            Ushort3 corePix,float rgbFac,float sdOrientationKernel,
            ColorSpace[] spaces)
        {
            int i,j,k;
            Ribbon rib;
            SurfaceBoundList sbl;
            SurfaceBound sb;
            ParsedObject po;
            for(i=0;i<Objects.Count;i++)
            {
                po= Objects[i];
                sbl = new SurfaceBoundList(Objects[i],dPix);
                sbl.CalcRibbons(corePix,rgbFac,heightRatio,sdOrientationKernel,spaces);
                for(j=0;j<sbl.Count;j++)
                {
                    sb = sbl[j];
                    if(sb.IsValidSurface)
                        for(k=0;k<sb.Ribbons.Length;k++)
                        {
                            rib = sb.Ribbons[k];
                            if(rib.RibbonWidth>=minimumLength)
                            {
                                rib.CatData = po.CatData;
                                rib.PoName = po.Name;
                                rib.PoDiameter = Math.Sqrt(po.Area);
                                rib.CategoryName = po.Category.Name;
                                rib.ProjName = po.Category.Owner.ShortProjname;
                                ribbons.Add(rib);
                            }
                        }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetFullSurfaceRibbons(true,ribbons,minimumLength,dPix,heightRatio,
                        corePix,rgbFac,sdOrientationKernel,
                        spaces);
                }
            }
        }
        public void GetFullSurfaceRibbons(bool doRecursion,
            ArrayList ribbons,int minimumLength,float dPix,
            Ushort3 corePix,float rgbFac,float[] directionalDistances,float sdOrientationKernel,
            ColorSpace[] spaces,ComplexD[][,] filters,int filterLength,PointF[] filterOffsets)
        {
            int i,j,k;
            Ribbon rib;
            SurfaceBoundList sbl;
            SurfaceBound sb;
            ParsedObject po;
            for(i=0;i<Objects.Count;i++)
            {
                po= Objects[i];
                sbl = new SurfaceBoundList(Objects[i],dPix);
                sbl.CalcRibbons(corePix,rgbFac,directionalDistances,sdOrientationKernel,spaces,filters,filterLength,filterOffsets);
                for(j=0;j<sbl.Count;j++)
                {
                    sb = sbl[j];
                    if(sb.IsValidSurface)
                        for(k=0;k<sb.Ribbons.Length;k++)
                        {
                            rib = sb.Ribbons[k];
                            if(rib.RibbonWidth>=minimumLength)
                            {
                                rib.CatData = po.CatData;
                                rib.PoName = po.Name;
                                rib.CategoryName = po.Category.Name;
                                rib.ProjName = po.Category.Owner.ShortProjname;
                                ribbons.Add(rib);
                            }
                        }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetFullSurfaceRibbons(true,ribbons,minimumLength,dPix,
                        corePix,rgbFac,directionalDistances,sdOrientationKernel,
                        spaces,filters,filterLength,filterOffsets);
                }
            }
        }
        /// <summary>
        /// For each ParsedObject in this.Objects, its SurfaceBoundList is evaluated, and the best
        /// ribbon section is returned (if it meets the minimum length requirement).  The section
        /// is centered.
        /// </summary>
        /// <param name="doRecursion">If true, the sub-categories are evaluated also.</param>
        /// <param name="ribbons"></param>
        /// <param name="sections"></param>
        /// <param name="minimumLength"></param>
        /// <param name="dPix"></param>
        /// <param name="corePixels"></param>
        /// <param name="directionalDistances"></param>
        /// <param name="sdCurvatureKernel"></param>
        /// <param name="spaces">The color spaces for which the ribbons must be computed.</param>
        public void GetBestFullRibbonSections(bool doRecursion,
            ArrayList ribbons,ArrayList sections,int minimumLength,float dPix,
            Ushort3 corePix,float rgbFac,float[] directionalDistances,float sdCurvatureKernel,
            ColorSpace[] spaces,ComplexD[][,] filters,int filterLength,PointF[] filterOffsets)
        {
            int i;
            Ribbon rib;
            int[] pos;
            for(i=0;i<Objects.Count;i++)
            {
                SurfaceBoundList sbl = new SurfaceBoundList(Objects[i],dPix);
                sbl.CalcRibbons(corePix,rgbFac,directionalDistances,sdCurvatureKernel,spaces,filters,filterLength,filterOffsets);
                if(sbl.IsValid && sbl.BestSection(out rib,out pos))
                {
                    if(pos[1]-pos[0]+1>=minimumLength)
                    {
                        ribbons.Add(rib);
                        sections.Add(pos);
                    }
                }
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                {
                    Sub[i].GetBestFullRibbonSections(true,
                        ribbons,sections,minimumLength,dPix,
                        corePix,rgbFac,directionalDistances,sdCurvatureKernel,spaces,filters,filterLength,filterOffsets);
                }
            }
        }
        /// <summary>
        /// For any members of Objects, execute Objects[.].RemoveSwallowedBy().
        /// </summary>
        public void RemoveSwallowedBy(bool doRecursion)
        {
            int i;
            for(i=0;i<Objects.Count;i++)
                Objects[i].RemoveSwallowedBy();
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].RemoveSwallowedBy(true);
        }
        /// <summary>
        /// For any members of Objects, execute Objects[.].RestoreSwallowedBy().
        /// </summary>
        public void RestoreSwallowedBy(bool doRecursion)
        {
            int i;
            for(i=0;i<Objects.Count;i++)
                Objects[i].RestoreSwallowedBy();
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].RestoreSwallowedBy(true);
        }
        /// <summary>
        /// Computes the mean of each pixel in the Log-Lms-based opponent space by relying on nPix random pixels
        /// from this ParsedObject.  The space consists of 5 elements:  Lum, Yb, Rg, Hue, Sat.
        /// </summary>
        /// <param name="nPix">The desired number of pixels per object.</param>
        /// <param name="mean">Appends a 5-element mean (double[]) for each member of this.Objects.  The color-space
        /// consists of 5 ordered elements:  Lum, Yb, Rg, Hue, Sat.</param>
        /// <param name="cov">Appends a 5 x 5 covariance matrix (double[,]) for each member of this.Objects.  The color-space
        /// consists of 5 ordered elements:  Lum, Yb, Rg, Hue, Sat.</param>
        /// <param name="nActual">Appends actual number of pixels evaluated for each object (int).</param>
        /// <returns>The number of ParsedObjects evaluated.</returns>
        public int LmsOppStats(
            int nPixPerObject,ArrayList means,ArrayList covs,ArrayList nActual,ParsedObjectList obs,
            Ushort3 pixelValues,float rgbFac,bool doRecursion)
        {
            int i,nObs=0;
            double[] mean;
            double[,] cov;
            for(i=0;i<this.Objects.Count;i++)
            {
                nActual.Add(Objects[i].LmsOppStats(nPixPerObject,out mean,out cov,pixelValues,rgbFac));
                means.Add(mean);
                covs.Add(cov);
                obs.Add(Objects[i]);
                nObs++;
            }
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    nObs += Sub[i].LmsOppStats(nPixPerObject,means,covs,nActual,obs,pixelValues,rgbFac,true);
            return nObs;
        }
        /// <summary>
        /// Converts this instance to a matlab structure of the given variable name.  The variable 'temp' in the matlab workspace is cleared.
        /// </summary>
        /// <param name="variableName">Contains everything appended before the fieldnames of the structure.  For example, "struct_name(1)."</param>
        /// <param name="matlab"></param>
        public void ToMatlabStruct(string varName,EngMATLib.EngMATAccess mat)
        {
            int i;
            //	Name
            mat.EvaluateAsString(varName + "Name='" + this.Name + "';");
            //	Objects
            if(this.Objects.Count>0)
                for(i=0;i<Objects.Count;i++)
                    Objects[i].ToMatlabStruct(varName + "Objects(" + (i+1).ToString() + ").",mat);
            else
                mat.EvaluateAsString(varName + "Objects=[];");
            //	Subcategories
            if(this.Sub.Count>0)
                for(i=0;i<Sub.Count;i++)
                    Sub[i].ToMatlabStruct(varName + "Sub(" + (i+1).ToString() + ").",mat);
            else
                mat.EvaluateAsString(varName + "Sub=[];");
        }
        public string Tooltip
        {
            get
            {
                int[][] conInventory = ContourCategorizationData.EmptyInventory;
                int[][] objInventory = ObjectCategorizationData.EmptyInventory;
                this.IncrementInventory(conInventory,objInventory,true);
                return CategoryNode.TooltipText(conInventory,objInventory,this.Ncontours(true),this.Nobjects);
            }
        }
        public static string TooltipText(int[][] conInventory,int[][] objInventory,int nContours,int nObjects)
        {
            return nObjects.ToString() +
				    " Parsed Objects\n------------\n" + 
				    ObjectCategorizationData.InventoryTooltipText(objInventory) + 
				    "\n " + nContours.ToString() + " Contours\n------------\n" + 
				    ContourCategorizationData.InventoryTooltipText(conInventory);
        }
        public int Nobjects
        {
            get
            {
                int output = Objects.Count;
                for(int i=0;i<this.Sub.Count;i++)
                    output += Sub[i].Nobjects;
                return output;
            }
        }
        public int Ncontours(bool doRecursion)
        {
            int output = 0;
            int i,j;
            for(i=0;i<this.Objects.Count;i++)
                for(j=0;j<this.Objects[i].Polygons.Count;j++)
                    output += this.Objects[i].Polygons[j].Contours.Count;
            if(doRecursion)
                for(i=0;i<Sub.Count;i++)
                    output += Sub[i].Ncontours(true);
            return output;
        }
        public void IncrementInventory(int[][] conInventory,int[][] objInventory,bool doRecursion)
        {
            int i;
            for(i=0;i<Objects.Count;i++)
            {
                Objects[i].IncrementContourInventory(conInventory);
                Objects[i].IncrementObjectInventory(objInventory);
            }
            if(doRecursion)
            {
                for(i=0;i<Sub.Count;i++)
                    Sub[i].IncrementInventory(conInventory,objInventory,true);
            }
        }
        public void MergeEdges(bool doRecursion)
        {
            int i,j;
            for(i=0;i<this.Objects.Count;i++)
                for(j=0;j<this.Objects[i].Polygons.Count;j++)
                    Objects[i].Polygons[j].MergeEdges();
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].MergeEdges(true);
        }
        public void PaintShadows(bool doRecursion, Color col)
        {
            ParsedObject po;
            PolygonF poly;
            ContourF con;
            int i,j,k,l;
            for(i=0;i<this.Objects.Count;i++)
                for(j=0;j<this.Objects[i].Polygons.Count;j++)
                {
                    po = this.Objects[i];
                    for(k=0;k<po.Polygons.Count;k++)
                    {
                        poly = po.Polygons[k];
                        for(l=0;l<poly.Contours.Count;l++)
                        {
                            con = poly.Contours[l];
                            if(con.CatData.ShadowBorder==3)
                                con.Color = col;
                        }
                    }
                }
            if(doRecursion)
                for(i=0;i<this.Sub.Count;i++)
                    Sub[i].PaintShadows(true,col);
        }
    }
}